package while_lang.tests;

import junit.framework.TestCase;
import while_lang.ast.Addition;
import while_lang.ast.Array;
import while_lang.ast.CompEqual;
import while_lang.ast.CompEqualOrLess;
import while_lang.ast.Conjunction;
import while_lang.ast.Constant;
import while_lang.ast.Division;
import while_lang.ast.Multiplication;
import while_lang.ast.Negation;
import while_lang.ast.Stmt;
import while_lang.ast.Subtraction;
import while_lang.ast.Variable;
import while_lang.parser.ParserCup;
import while_lang.semantics.State;

public class EvaluationTests extends TestCase {

	public void testState() {
		State state = new State();
		try {
			state.get("x");
			fail("Getting the value of 'x' from an empty state should have failed!");
		} catch (Throwable err) {
			assertTrue(err instanceof Error);
		}
		assertEquals(1.0, state.set("x", 1.0));
		assertEquals(1.0, state.get("x"));
		assertEquals(1.2, state.set("x", 1.2));
		assertEquals(1.2, state.get("x"));
		
		assertEquals(true, state.set("b", true));
		assertEquals(true, state.get("b"));
		assertEquals(false, state.set("b", false));
		assertEquals(false, state.get("b"));
		
		state = new State("x", 3.1416, "b", true);
		assertEquals(3.1416, state.get("x"));
		assertEquals(true, state.get("b"));
	}
	
	public static void assertAprox(double expected, Object actual) {
		assertTrue(actual instanceof Double);
		assertTrue("expected:<"+ expected +"> but was:<"+ actual +">",
			Math.abs(expected - (Double)actual) < 1e-16);
	}
	
	public void testExpressions() throws Exception {
		State state = new State(
			"x", 3.1416, 
			"b", true, 
			"v", new Object[]{1.0, 1.2}
		);
		
		Constant k1_0 = new Constant(1.0);
		Constant k1_2 = new Constant(1.2);
		Constant kTrue = new Constant(true);
		Constant kFalse = new Constant(false);
		assertEquals(1.0, k1_0.evaluate(state));
		assertEquals(1.2, k1_2.evaluate(state));
		assertEquals(true, kTrue.evaluate(state));
		assertEquals(false, kFalse.evaluate(state));
		
		Variable varX = new Variable("x");
		Variable varB = new Variable("b");
		Variable varV = new Variable("v");
		assertEquals(3.1416, varX.evaluate(state));
		assertEquals(true, varB.evaluate(state));

		assertAprox(2.2, new Addition(k1_0, k1_2).evaluate(state));
		assertAprox(2.2, new Addition(k1_2, k1_0).evaluate(state));
		assertAprox(4.1416, new Addition(varX, k1_0).evaluate(state));
		assertAprox(-0.2, new Subtraction(k1_0, k1_2).evaluate(state));
		assertAprox(+0.2, new Subtraction(k1_2, k1_0).evaluate(state));
		assertAprox(2.1416, new Subtraction(varX, k1_0).evaluate(state));
		
		assertAprox(1.2, new Multiplication(k1_0, k1_2).evaluate(state));
		assertAprox(1.2, new Multiplication(k1_2, k1_0).evaluate(state));
		assertAprox(3.76992, new Multiplication(varX, k1_2).evaluate(state));
		assertAprox(1.2, new Division(k1_2, k1_0).evaluate(state));
		assertAprox(3.1416, new Division(varX, k1_0).evaluate(state));
		assertAprox(2.618, new Division(varX, k1_2).evaluate(state));
		
		assertEquals(true, new CompEqual(varX, varX).evaluate(state));
		assertEquals(false, new CompEqual(varX, k1_2).evaluate(state));
		assertEquals(true, new CompEqualOrLess(k1_0, k1_2).evaluate(state));
		assertEquals(false, new CompEqualOrLess(k1_2, k1_0).evaluate(state));
		
		assertEquals(false, new Negation(varB).evaluate(state));
		assertEquals(true, new Negation(new Negation(varB)).evaluate(state));
		assertEquals(true, new Conjunction(varB, varB).evaluate(state));
		assertEquals(false, new Conjunction(new Negation(varB), varB).evaluate(state));
		assertEquals(false, new Conjunction(varB, new Negation(varB)).evaluate(state));
		
		assertEquals(1.2, new Array(varV, k1_0).evaluate(state));
		assertEquals(1.2, new Array(varV, k1_2).evaluate(state));
		assertEquals(1.0, new Array(varV, new Constant(0.0)).evaluate(state));
	}
	
	public void testStatements() throws Exception {
		State state = new State();
		Stmt stmt;
		stmt = ParserCup.parseString("{x = 10;}");
		state = stmt.evaluate(state);
		
		assertEquals(state.get("x"),10.0);

		
		stmt = ParserCup.parseString("{ x = 10; f = 1; while (1 <= x) do { f = f * x; x = x - 1; }}");
		state = stmt.evaluate(state);
		
		assertEquals(state.get("f"),3628800.0);
		assertEquals(state.get("x"),0.0);

	}
	
}
